﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlClient;
using System.Data;

namespace ATM
{
    class ATM
    {
        private static string ConnectionString = "Server=.; Database = ATM; Integrated Security = true;";
        private static int CardNumberLength = 16;
        private static int PINLength = 4;
        private static decimal MinimumAmmountToWithdraw = 10m;

        static string EnterCardNumber()
        {
            Console.WriteLine("Please enter your card or type your card number.");
            Console.Write("> ");
            string cardNumber = Console.ReadLine();
            while (cardNumber.Length != CardNumberLength)
            {
                Console.WriteLine("{0} is not a correct card number!", cardNumber);
                Console.WriteLine("Please enter your card or type your card number.");
                Console.Write("> ");
                cardNumber = Console.ReadLine();
            }
            return cardNumber;
        }

        static string EnterPIN()
        {
            Console.WriteLine("Please enter your PIN.");
            Console.Write("> ");
            string PIN = Console.ReadLine();
            while (PIN.Length != PINLength)
            {
                Console.WriteLine("{0} is not a correct PIN!", PIN);
                Console.WriteLine("Please enter your PIN.");
                Console.Write("> ");
                PIN = Console.ReadLine();
            }
            return PIN;
        }

        static decimal EnterAmmount()
        {
            Console.WriteLine("Please enter the required ammount.");
            Console.Write("> ");
            string ammountString = Console.ReadLine();
            decimal ammount;
            while (!decimal.TryParse(ammountString, out ammount))
            {
                Console.WriteLine("{0} is not a correct ammount.");
                Console.WriteLine("Please enter the required ammount.");
                Console.Write("> ");
                ammountString = Console.ReadLine();
            }
            return ammount;
        }

        static void WithdrawCash()
        {
            string cardNumber = EnterCardNumber();
            decimal ammount = EnterAmmount();
            string PIN = EnterPIN();

            PerformTransaction(cardNumber, PIN, ammount);
        }

        private static void ShowWithdrawHistory()
        {
            string cardNumber = EnterCardNumber();
            string PIN = EnterPIN();

            PerformHistoryTransaction(cardNumber, PIN);
        }

        private static void PerformTransaction(string cardNumber, string PIN, decimal ammount)
        {
            SqlConnection dbCon = new SqlConnection(ConnectionString);
            dbCon.Open();
            using (dbCon)
            {
                SqlTransaction tran = dbCon.BeginTransaction(IsolationLevel.RepeatableRead);

                SqlCommand command = dbCon.CreateCommand();
                command.Transaction = tran;
                try
                {
                    command.CommandText = "SELECT COUNT(*) FROM CardAccounts " +
                        "WHERE CardNumber = @cardNumber";
                    command.Parameters.Add(new SqlParameter("@cardNumber", cardNumber));
                    int cardsFound = (int)command.ExecuteScalar();
                    if (cardsFound != 1)
                    {
                        throw new ArgumentException("Wrong card number!");
                    }

                    command.CommandText = "SELECT COUNT(*) FROM CardAccounts " +
                        "WHERE CardNumber = @cardNumber AND CardPIN = @cardPin";
                    command.Parameters.Add(new SqlParameter("@cardPin", PIN));
                    cardsFound = (int)command.ExecuteScalar();
                    if (cardsFound != 1)
                    {
                        throw new ArgumentException("Wrong PIN!");
                    }

                    if (ammount < MinimumAmmountToWithdraw)
                    {
                        throw new ArgumentException(String.Format("You can not withdraw less than {0}.",
                            MinimumAmmountToWithdraw));
                    }

                    command.CommandText = "SELECT CardCash FROM CardAccounts " +
                    "WHERE CardNumber = @cardNumber AND CardPIN = @cardPin";
                    decimal cardAmmount = (decimal)command.ExecuteScalar();
                    if (cardAmmount < ammount)
                    {
                        throw new ArgumentException("You do not have enough money!");
                    }

                    decimal newAmmount = cardAmmount - ammount;
                    command.CommandText = "UPDATE CardAccounts " +
                        "SET CardCash = @newAmmount " +
                        "WHERE CardNumber = @cardNumber AND CardPIN = @cardPin";
                    command.Parameters.Add(new SqlParameter("@newAmmount", newAmmount));
                    command.ExecuteNonQuery();

                    command.CommandText = "INSERT INTO AccountTransactions " +
                        "VALUES(@cardNumber, @date, @ammount)";
                    command.Parameters.Add(new SqlParameter("@date", DateTime.Now));
                    command.Parameters.Add(new SqlParameter("@ammount", ammount));
                    command.ExecuteNonQuery();
                   
                    tran.Commit();
                    Console.WriteLine("Now you can take your {0}.", ammount);
                }
                catch (ArgumentException ex)
                {
                    Console.WriteLine(ex.Message);
                    tran.Rollback();
                }
                catch (SqlException ex)
                {
                    Console.WriteLine(ex.Message);
                    tran.Rollback();
                }
                finally
                {
                    Console.WriteLine("Have a nice day!");
                }
            }
        }

        private static void PerformHistoryTransaction(string cardNumber, string PIN)
        {
            SqlConnection dbCon = new SqlConnection(ConnectionString);
            dbCon.Open();
            using (dbCon)
            {
                SqlTransaction tran = dbCon.BeginTransaction(IsolationLevel.RepeatableRead);

                SqlCommand command = dbCon.CreateCommand();
                command.Transaction = tran;
                try
                {
                    command.CommandText = "SELECT COUNT(*) FROM CardAccounts " +
                        "WHERE CardNumber = @cardNumber";
                    command.Parameters.Add(new SqlParameter("@cardNumber", cardNumber));
                    int cardsFound = (int)command.ExecuteScalar();
                    if (cardsFound != 1)
                    {
                        throw new ArgumentException("Wrong card number!");
                    }

                    command.CommandText = "SELECT COUNT(*) FROM CardAccounts " +
                        "WHERE CardNumber = @cardNumber AND CardPIN = @cardPin";
                    command.Parameters.Add(new SqlParameter("@cardPin", PIN));
                    cardsFound = (int)command.ExecuteScalar();
                    if (cardsFound != 1)
                    {
                        throw new ArgumentException("Wrong PIN!");
                    }

                    command.CommandText = "SELECT TransactionDate, Ammount FROM AccountTransactions " +
                        "WHERE CardNumber = @cardNumber " +
                        "ORDER BY TransactionDate";
                    SqlDataReader reader = command.ExecuteReader();
                    using (reader)
                    {
                        Console.WriteLine("Card {0} withdraw history:", cardNumber);
                        bool historyExists = false;
                        while (reader.Read())
                        {
                            historyExists = true;
                            DateTime transactionDate = (DateTime)reader["TransactionDate"];
                            decimal ammount = (decimal)reader["Ammount"];
                            Console.WriteLine("{0}: {1:0.00}", transactionDate, ammount);
                        }
                        if (!historyExists)
                        {
                            Console.WriteLine("No history found!");
                        }
                    }
                    
                    tran.Commit();
                }
                catch (ArgumentException ex)
                {
                    Console.WriteLine(ex.Message);
                    tran.Rollback();
                }
                catch (SqlException ex)
                {
                    Console.WriteLine(ex.Message);
                    tran.Rollback();
                }
                finally
                {
                    Console.WriteLine("Have a nice day!");
                }
            }
        }

        private static void UseATM()
        {
            Console.WriteLine("Welcome!");
            string choice;
            do
            {
                Console.WriteLine("Choose service (1 or 2):");
                Console.WriteLine("1: Withdraw cash");
                Console.WriteLine("2: Withdraw history");
                Console.Write("> ");
                choice = Console.ReadLine();
            }while(choice != "1" && choice != "2");
            if(choice == "1")
            {
                WithdrawCash();
            }
            else
            {
                ShowWithdrawHistory();
            }
        }


        static void Main()
        {
            UseATM();
        }
    }
}
